package org.sandrop.webscarab.plugin.proxy;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.URL;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;

import org.apache.http.conn.util.InetAddressUtils;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.AssetManager;
import android.util.Base64;
import android.util.Log;

import com.google.gson.Gson;

public class Utils {

	public static class AppDescriptor {

		private String packageName;
		private String version;
		private String baseURL;
		
		public AppDescriptor(String pName, String ver, String base) {
			packageName = pName;
			version = ver;
			baseURL = base;
		}
		
		public String getPackageName() {
			return packageName;
		}
		
		public String getVersion() {
			return version;
		}
		
		public String getBaseURL() {
			return baseURL;
		}
		
		public void setBaseURL(String base) {
			baseURL = base;
		}
		
		@Override
		public boolean equals(Object o) {
			if (o instanceof AppDescriptor) {
				boolean c1 = ((AppDescriptor) o).packageName.compareTo(this.packageName) == 0;
				boolean c2 = ((AppDescriptor) o).version.compareTo(this.version) == 0;
				boolean c3 = ((AppDescriptor) o).baseURL.compareTo(this.baseURL) == 0;
				
				return c1 && c2 && c3;
			}
		
			return false;
		}
		
	}
	
	private static final String TCP_4_FILE_PATH 	= "/proc/net/tcp";
	private static final String TCP_6_FILE_PATH 	= "/proc/net/tcp6";
	
	// (address) (port) (pid)
	private static final String TCP_6_PATTERN 	= "\\d:\\s([0-9A-F]{32}):([0-9A-F]{4})\\s[0-9A-F]{32}:[0-9A-F]{4}\\s[0-9A-F]{2}\\s[0-9]{8}:[0-9]{8}\\s[0-9]{2}:[0-9]{8}\\s[0-9]{8}\\s([0-9]+)";
	
	// (address) (port) (pid)
	private static final String TCP_4_PATTERN 	= "\\d:\\s([0-9A-F]{8}):([0-9A-F]{4})\\s[0-9A-F]{8}:[0-9A-F]{4}\\s[0-9A-F]{2}\\s[0-9A-F]{8}:[0-9A-F]{8}\\s[0-9]{2}:[0-9]{8}\\s[0-9A-F]{8}\\s\\s([0-9]+)";

	
	@SuppressWarnings("unused")
	public static AppDescriptor getPackageFromPort(Context context, int port) {
		try {
			
			String ipv4Address = getIPAddress(true);
			String ipv6Address = getIPAddress(false);

			boolean hasIPv6 = (ipv6Address.length() > 0); //TODO use this value to skip ipv6 check, eventually
			
			File tcp = new File(TCP_6_FILE_PATH);
			BufferedReader reader = new BufferedReader(new FileReader(tcp));
			String line = "";
			StringBuilder builder = new StringBuilder();
			
			while ((line = reader.readLine()) != null) {
				builder.append(line);
			}
			
			String content = builder.toString();
			
			Matcher m6 = Pattern.compile(TCP_6_PATTERN, Pattern.CASE_INSENSITIVE | Pattern.UNIX_LINES | Pattern.DOTALL).matcher(content);
			
			while (m6.find()) {
				String addressEntry = m6.group(1);
				String portEntry 	= m6.group(2);
				int pidEntry 	= Integer.valueOf(m6.group(3));
				
				if (Integer.parseInt(portEntry, 16) == port) {
					
					System.out.println("found something v6");
					
					PackageManager manager = context.getPackageManager();
					String[] packagesForUid = manager.getPackagesForUid(pidEntry);
					
					if (packagesForUid != null) {
						String packageName = packagesForUid[0];
						PackageInfo pInfo = manager.getPackageInfo(packageName, 0);
						String version = pInfo.versionName;

						return new AppDescriptor(packageName, version, null);
					}
				}
				
			}
			
			// this means that no connection with that port could be found in the tcp6 file
			// try the tcp one
			
			tcp = new File(TCP_4_FILE_PATH);
			reader = new BufferedReader(new FileReader(tcp));
			line = "";
			builder = new StringBuilder();
			
			while ((line = reader.readLine()) != null) {
				builder.append(line);
			}
			
			content = builder.toString();
			
			Matcher m4 = Pattern.compile(TCP_4_PATTERN, Pattern.CASE_INSENSITIVE | Pattern.UNIX_LINES | Pattern.DOTALL).matcher(content);
			
			while (m4.find()) {
				String addressEntry = m4.group(1);
				String portEntry 	= m4.group(2);
				int pidEntry 	= Integer.valueOf(m4.group(3));
				
				portEntry = convertHexToString(portEntry); // hex to ascii
				
				System.out.println("found something v4");
				
				if (Integer.valueOf(portEntry) == port) {
					PackageManager manager = context.getPackageManager();
					String[] packagesForUid = manager.getPackagesForUid(pidEntry);
					
					if (packagesForUid != null) {
						String packageName = packagesForUid[0];
						PackageInfo pInfo = manager.getPackageInfo(packageName, 0);
						String version = pInfo.versionName;

						return new AppDescriptor(packageName, version, null);
					}
				}
			}
			
		} catch (SocketException e) {
			e.printStackTrace();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (NameNotFoundException e) {
			e.printStackTrace();
		} catch (Exception e) {//FIXME added because the integer conversion of the port was failing. probably exception in regex. check it out.
			e.printStackTrace();
		}

		return null;
	}
	
	private static String convertHexToString(String hex) {
		 
		  StringBuilder sb = new StringBuilder();
		  StringBuilder temp = new StringBuilder();
	 
		  //49204c6f7665204a617661 split into two characters 49, 20, 4c...
		  for( int i=0; i<hex.length()-1; i+=2 ){
	 
		      //grab the hex in pairs
		      String output = hex.substring(i, (i + 2));
		      //convert hex to decimal
		      int decimal = Integer.parseInt(output, 16);
		      //convert the decimal to character
		      sb.append((char)decimal);
	 
		      temp.append(decimal);
		  }
		  System.out.println("Decimal : " + temp.toString());
	 
		  return sb.toString();
	  }
	
	
	@SuppressLint("DefaultLocale")
	public static String getIPAddress(boolean useIPv4) throws SocketException {
		List<NetworkInterface> interfaces = Collections.list(NetworkInterface.getNetworkInterfaces());
		for (NetworkInterface intf : interfaces) {
			List<InetAddress> addrs = Collections.list(intf.getInetAddresses());
			for (InetAddress addr : addrs) {
				if (!addr.isLoopbackAddress()) {
					String sAddr = addr.getHostAddress().toUpperCase();
					boolean isIPv4 = InetAddressUtils.isIPv4Address(sAddr);
					
					if (useIPv4) {
						if (isIPv4) 
							return sAddr;
					} else {
						if (!isIPv4) {
							if (sAddr.startsWith("fe80") || sAddr.startsWith("FE80")) // skipping link-local addresses
								continue;
							
							int delim = sAddr.indexOf('%'); // drop ip6 port suffix
							return delim<0 ? sAddr : sAddr.substring(0, delim);
						}
					}
				}
			}
		}
		
		return "";
	}

	public static X509Certificate[] getPeerCertificatesFromProxy(String url, int serial) {
		try {
			
			Log.d("ASE2", "(" + serial + ") " + "proxy URL : " + url);
			
			URL remoteURL = new URL(url);
			HttpsURLConnection urlConnection = (HttpsURLConnection) remoteURL.openConnection();
			urlConnection.connect();
			return (X509Certificate[]) urlConnection.getServerCertificates();
			
		} catch (IOException e) {
			e.printStackTrace();
		}

		return null;
	}
	
	@SuppressWarnings("unchecked")
	public static X509Certificate[] getPeerCertificatesFromTTP(Context context, String url, int serial) {
		try {
			
			Log.d("ASE2", "(" + serial + ") " + "remote URL : " + url);
			
			AssetManager assetManager       = context.getAssets();
			InputStream keyStoreInputStream = assetManager.open("ase.store");
			KeyStore trustStore             = KeyStore.getInstance("BKS");
			
			trustStore.load(keyStoreInputStream, "mithys".toCharArray()); //FIXME change store passowrd!!!!!!
			
			TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
			tmf.init(trustStore);
			
			SSLContext sslContext = SSLContext.getInstance("TLS");
			sslContext.init(null, tmf.getTrustManagers(), null);
			
			java.net.Proxy proxy = new java.net.Proxy(java.net.Proxy.Type.HTTP, new InetSocketAddress("localhost", 9008));
			
			URL remoteURL = new URL("https://ec2-54-245-197-121.us-west-2.compute.amazonaws.com:8443/ASE@AWS/get_ssl_certificate");
			HttpsURLConnection urlConnection = (HttpsURLConnection) remoteURL.openConnection(); //FIXME pass 'proxy' argument
			urlConnection.setDoOutput(true);
			urlConnection.setSSLSocketFactory(sslContext.getSocketFactory());

			urlConnection.getOutputStream().write((url + "&" + "GET").getBytes());
			urlConnection.getOutputStream().close();
			
			BufferedReader reader = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));
			StringBuffer buffer = new StringBuffer();
			String line = "";
			
			while ((line = reader.readLine()) != null)
				buffer.append(line);
			
			reader.close();

			Gson gson = new Gson();
			ArrayList<String> encodedCerts = gson.fromJson(buffer.toString(), new ArrayList<String>().getClass());
			ArrayList<X509Certificate> certs = new ArrayList<X509Certificate>();
			
			for (String encodedCertificate : encodedCerts) {

				byte[] decodedCert = Base64.decode(encodedCertificate, 0);
				
				CertificateFactory factory = CertificateFactory.getInstance("X.509");
				X509Certificate cert = (X509Certificate) factory.generateCertificate(new ByteArrayInputStream(decodedCert));
				
				certs.add(cert);
			}
			
			X509Certificate[] result = new X509Certificate[encodedCerts.size()];
			
			for (int i=0; i < certs.size(); i++)
				result[i] = certs.get(i);
			
			return result;
			
		} catch (MalformedURLException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		} catch (CertificateException e) {
			e.printStackTrace();
		} catch (KeyStoreException e) {
			e.printStackTrace();
		} catch (KeyManagementException e) {
			e.printStackTrace();
		}
		
		return null;
	}
	
	public static boolean compareX509Certificates(X509Certificate[] proxyObtainedCerts, X509Certificate[] remoteObtainedCerts, int serial) {
		
		if (proxyObtainedCerts == null || remoteObtainedCerts == null)
			return false;
		
		if (proxyObtainedCerts.length != remoteObtainedCerts.length)
			return false;
		
		for (int i=0; i < proxyObtainedCerts.length; i++) {
			X509Certificate proxyCert = proxyObtainedCerts[i];
			X509Certificate remoteCert = remoteObtainedCerts[i];
			try {
//				proxyCert.verify(proxyCert.getPublicKey()); //FIXME implement proper signature validation
//				remoteCert.verify(remoteCert.getPublicKey()); //FIXME implement proper signature validation
				
				Log.d("ASE2", "(" + serial + ") " + "proxy  cert DN: " + proxyCert.getSubjectDN());
				Log.d("ASE2", "(" + serial + ") " + "remote cert DN: " + remoteCert.getSubjectDN());
				
				String proxyCertSig = new String(proxyCert.getSignature());
				String remoteCertSig = new String(remoteCert.getSignature());
				
				Log.d("ASE2", "(" + serial + ") " + "proxy  sig: " + proxyCertSig);
				Log.d("ASE2", "(" + serial + ") " + "remote sig: " + remoteCertSig);
				
				if (proxyCertSig.compareTo(remoteCertSig) != 0)
					return false;
				
			} catch (Exception e) {
				e.printStackTrace();
			}
			
		}

		return true;
		
	}
	
	
}
